from eden.evo.chromosome import Chromosome
from eden.evo.codon import Codon
from eden.evo.context import Context
import doctest
import random

class Mutator:
    def __init__(self, ratio, encoding):
        self.__ratio = ratio
        self.__encoding = encoding
        
    def getratio(self):
        return self.__ratio
    
    def getencoding(self):
        return self.__encoding
    
    def generate_null_codon(self):
        return Codon(0, 128, 0)
    
    def mutate(self, chromosome):
        newcodons = []
        for codon in chromosome:
            if random.random() < self.__ratio:
                mutated = self.mutate_codon(codon)
                newcodons.extend(mutated)
            else:
                newcodons.append(codon)
        return Chromosome(newcodons)
    
class ChangeMutator(Mutator):
    def mutate_codon(self, codon):
        return [Codon(self.mutate_operator(codon.operator) if random.randint(1, 3) == 1 else codon.operator,
                self.mutate_loperand(codon.loperand) if random.randint(1, 3) == 1 else codon.loperand,
                self.mutate_roperand(codon.roperand) if random.randint(1, 3) == 1 else codon.roperand)]
        
    def mutate_operator(self, base):
        '''
        >>> from eden.evo.aminos import *
        >>> encoding = { 0 : NullAmino, 1 : AddAmino }
        >>> mutator = ChangeMutator(1, encoding)
        >>> result = mutator.mutate_operator(0)
        >>> result in encoding.keys()
        True
        '''
        return random.choice(self.getencoding().keys())
    
    def mutate_loperand(self, base):
        '''
        >>> mutator = ChangeMutator(1, {})
        >>> result = mutator.mutate_loperand(128)
        >>> result in range(Context.MIN_REGISTER_INDEX, Context.MAX_REGISTER_INDEX + 1)
        True
        '''
        return random.randint(Context.MIN_REGISTER_INDEX, Context.MAX_REGISTER_INDEX)
    
    def mutate_roperand(self, base):
        if base < 128: # ConstOperand
            return self.mutate_const_roperand(base)
        else: # RegisterOperand
            return self.mutate_register_roperand(base)
    
    def mutate_const_roperand(self, base):
        if random.randint(1, 5) == 1:
            # flip to RegisterOperand
            return random.randint(Context.MIN_REGISTER_INDEX, Context.MAX_REGISTER_INDEX)
        else:
            # mutate the value of the ConstOperand
            base = int(round(random.gauss(base, 4)))
            base %= 128
            return base
        
    def mutate_register_roperand(self, base):
        if random.randint(1, 5) == 1:
            # flip to ConstOperand
            return random.randint(0, 127)
        else:
            # mutate the index of the RegisterOperand
            return random.randint(Context.MIN_REGISTER_INDEX, Context.MAX_REGISTER_INDEX)
        
class InsertMutator(Mutator):
    def mutate_codon(self, codon):
        '''
        >>> mutator = InsertMutator(1, {})
        >>> original_codon = (1, 128, 2)
        >>> null_codon = mutator.generate_null_codon()
        >>> result = mutator.mutate_codon(original_codon)
        >>> possible_results = [[original_codon], [null_codon, original_codon], [original_codon, null_codon], [null_codon, original_codon, null_codon]]
        >>> result in possible_results
        True
        '''
        precodon = [self.generate_null_codon()] if random.randint(1, 2) == 1 else []
        postcodon = [self.generate_null_codon()] if random.randint(1, 2) == 1 else []
        return precodon + [codon] + postcodon

class DeleteMutator(Mutator):
    def mutate_codon(self, codon):
        return []
    
class OriginateMutator(Mutator):
    def __init__(self, ratio, encoding, origin):
        Mutator.__init__(self, ratio, encoding)
        self.origin = origin
        
    def mutate(self, chromosome):
        '''
        >>> from eden.evo.chromosome import *
        >>> origin = Chromosome((Codon(0, 128, 0), ))
        >>> mutator = OriginateMutator(1, {}, origin)
        >>> result = mutator.mutate(Chromosome(((1, 129, 3), )))
        >>> result == origin
        True
        '''
        if random.random() < self.getratio():
            return self.origin
        else:
            return chromosome
        
if __name__ == '__main__':
    doctest.testmod()